Skip to content

fix(bump): validate subtree count varint to bound parser allocations (#66)#129

Merged
galt-tr merged 3 commits into
mainfrom
fix/issue-66-block-parser-allocs
May 27, 2026
Merged

fix(bump): validate subtree count varint to bound parser allocations (#66)#129
galt-tr merged 3 commits into
mainfrom
fix/issue-66-block-parser-allocs

Conversation

@galt-tr
Copy link
Copy Markdown
Contributor

@galt-tr galt-tr commented May 3, 2026

Summary

  • Validate subtreeCount against a documented maxSubtreeCount = 10_000_000 cap before allocating; reject counts that cannot fit in the remaining body (32 bytes per hash). Closes F-008 ([F-008] untrusted block metadata drives large parser allocations #66).
  • Apply the same body-capacity check to cbBUMPLen, falling back to the existing "no coinbase BUMP" path so subtree hashes are still returned when the coinbase tail is bogus.
  • Follow-up commit (defense-in-depth): bound the txCount and sizeBytes metadata varints too. These are read but currently only consume bytes to advance the cursor — bounding them now (1e9 and 1 TiB respectively) costs one branch per parse and prevents a future refactor from creating a new allocation path on untrusted input.
  • Background: PR#107 capped the response body at 1 GiB, but the in-band varints were still trusted, so a hostile DataHub could send a 9-byte 0xFF varint encoding ~2^64-1 and force a multi-petabyte preallocation before io.LimitReader ran out of bytes.

Choice of caps

  • subtreeCount: DefaultMaxBlockBytes / 32 ≈ 33.5M is the absolute hard ceiling implied by the body cap. 10,000,000 is comfortably above any plausible Teranode-class block (millions of subtrees) while leaving 3x headroom.
  • txCount: 1,000,000,000 — well above any plausible block tx count.
  • sizeBytes: 1 TiB — a logical block-size limit, not a response-body limit (the body itself is already enforced by DefaultMaxBlockBytes).

Rebase notes (2026-05-27)

Rebased onto current main (40ac42e). The original commit (subtree/cbBUMP fix) replays cleanly against the current parseBlockBinary; the only conflict was in the constants region of bump/datahub.go, where main had since added the BlockDataValidator type — resolved by keeping both declarations side-by-side. The defense-in-depth txCount/sizeBytes additions are a follow-up commit on top.

Test plan

  • go build ./...
  • go vet ./...
  • go test ./bump/... -count=1 -race
  • Tests cover: huge subtreeCount rejected via fetcher, plausible-but-impossible subtreeCount rejected by body-capacity check, zero-count happy path still works, oversize cbBUMPLen short-circuits to nil coinbase BUMP, oversize txCount rejected, oversize sizeBytes rejected.

@galt-tr galt-tr requested a review from mrz1836 as a code owner May 3, 2026 13:51
@github-actions github-actions Bot added size/M Medium change (51–200 lines) bug-P3 Lowest rated bug, affects nearly none or low-impact labels May 3, 2026
@mrz1836 mrz1836 assigned galt-tr and unassigned mrz1836 May 5, 2026
galt-tr added 2 commits May 27, 2026 14:09
…66)

The block-binary parser fed two untrusted varints — subtreeCount and
cbBUMPLen — directly into make() without validating them. PR#107 capped
the body at 1 GiB but the in-band counts could still claim ~2^64
elements, forcing a multi-petabyte preallocation before the LimitReader
ever ran out of bytes. Reject implausible subtree counts (>10M, the
documented Teranode-class headroom) and counts that cannot physically
fit in the remaining body. Apply the same body-capacity sanity check to
cbBUMPLen, falling back to the existing "no coinbase BUMP" path so the
subtree hashes are still returned. Adds tests covering huge varints, the
body-capacity boundary, and the cbBUMPLen sibling path.

Closes #66
The /block binary response prefixes the payload with two metadata varints
(txCount, sizeBytes) that parseBlockBinary currently reads only to advance
the cursor. They are not used for allocation today, so they are not part of
the F-008 exploit surface — but a future refactor could begin to consume
them, and the cost of bounding them now is one branch per parse. Cap
txCount at 1e9 (well above any plausible Teranode block) and sizeBytes at
1 TiB (a logical block-size limit, not a response-body limit — the body
itself is enforced separately by DefaultMaxBlockBytes).

Adds two tests asserting that oversized varints are rejected.

Defense-in-depth follow-up to #66 / F-008.
@galt-tr galt-tr force-pushed the fix/issue-66-block-parser-allocs branch from 0d8e9e4 to 00d4ae2 Compare May 27, 2026 18:11
bytes.Reader.Len() is documented to return the number of unread bytes,
which is always non-negative. gosec G115 flags the int -> uint64 cast
defensively; add inline //nolint annotations with rationale, matching
the project's existing nolint:gosec style in datahub_test.go.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the DataHub /block/<hash> binary parser to treat in-band varints as untrusted input, preventing attacker-controlled counts/lengths from triggering extreme allocations or unrealistic parsing work, while preserving the existing best-effort behavior for returning subtree hashes when the coinbase tail is malformed.

Changes:

  • Added explicit upper bounds for txCount, sizeBytes, and subtreeCount, and validated subtreeCount against remaining-body capacity before preallocation.
  • Added a remaining-body capacity check for cbBUMPLen, falling back to the existing “no coinbase BUMP” path on oversize.
  • Added tests covering oversize varints and “plausible-but-impossible” body-capacity cases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
bump/datahub.go Adds max caps and remaining-body checks to prevent untrusted varints from driving large allocations in parseBlockBinary.
bump/datahub_test.go Adds regression tests for oversized varints and body-capacity validation behavior (F-008).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@galt-tr galt-tr merged commit 1826114 into main May 27, 2026
50 checks passed
@galt-tr galt-tr deleted the fix/issue-66-block-parser-allocs branch May 27, 2026 19:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-P3 Lowest rated bug, affects nearly none or low-impact size/M Medium change (51–200 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants